package com.kylin.common.security.config;

import com.kylin.common.core.beans.ResultBean;
import com.kylin.common.security.annotation.NoLogin;
import com.kylin.common.security.handler.AccessDeniedHandlerImpl;
import com.kylin.common.security.handler.AuthenticationEntryPointImpl;
import com.kylin.common.security.handler.LogoutHandlerImpl;
import com.kylin.common.security.handler.LogoutSuccessHandlerImpl;
import com.kylin.common.security.service.handler.DemoPasswordLoginHandler;
import com.kylin.common.security.service.handler.LoginHandlerFactory;
import com.kylin.common.security.util.ResponseUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.NonNull;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * SpringSecurity安全配置
 * @author wuhao
 * @version 1.0 - 2022/8/15
 * 新版本改造参考： https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter#ldap-authentication
 */
@RequiredArgsConstructor(onConstructor_ = @Autowired)
//@EnableWebSecurity(debug = true)
// 开发方法注解
@EnableGlobalMethodSecurity(prePostEnabled = true)
// 演示
@Import({DemoPasswordLoginHandler.class, LoginHandlerFactory.class})
@EnableConfigurationProperties({SecurityProperties.class, LoginProperties.class})
public class SecurityConfig {

    @NonNull
    private HttpSecurityConfig httpSecurityConfig;

    @Autowired
    AuthenticationEntryPointImpl authenticationEntryPoint;

    @Autowired
    AccessDeniedHandlerImpl accessDeniedHandler;

    @Autowired
    private LogoutHandlerImpl logoutHandler;

    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    @Autowired
    private SecurityProperties securityProperties;
    @Resource
    private ApplicationContext applicationContext;

    /**
     * 配置 HttpSecurity
     * @param http
     * @return
     * @author wuhao
     * @since 2.6.0 - 2022/10/6
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors().and()
                // 关闭csrf
                .csrf().disable()
                // 禁用form表单认证
                .formLogin().disable()
                // 不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //  自定义退出
                .logout().addLogoutHandler(logoutHandler).logoutSuccessHandler(logoutSuccessHandler)
                .and()
                .authorizeRequests()
                //允许所有人访问knife4j
                .antMatchers(getIgnoreUrls().toArray(new String[]{})).permitAll()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and().apply(httpSecurityConfig);

        // 认证失败和权限自定义异常处理
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);
        // 不通过session获取securityContext
//         http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        return http.build();
    }


    //启动过滤器链是很昂贵的，占用了系统很多资源，有时候我们经过一个路径（比如访问静态资源：图片，视频等），不需要进行认证和授权，也就不需要启动过滤器链，为了节约系统资源，可以通过重写configure(WebSecurity web)方法来禁用过滤器链
    public void configure(WebSecurity web) {
        web
                .ignoring()
                .antMatchers("/error",
                            "/resources/**")
                .antMatchers("/doc.html","/webjars/**","/img.icons/**","/swagger-resources/**","/**","/v2/api-docs")
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

    /**
     * 未授权处理
     * @return
     * @author wuhao
     * @since 2.6.0 - 2022/10/9
     */
    public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            ResponseUtils.out(response, ResultBean.onFail("未授权统一处理"));
        }
    }
    
    /** 获取免鉴权的地址 */
    private Set<String> getIgnoreUrls() {
        // 注解过滤
        Map<String, Object> beansWithAnnotationMap = applicationContext.getBeansWithAnnotation(RestController.class);
        Set<String> ignoreList = new HashSet<>();
        Class<? extends Object> clazz = null;
        for(Map.Entry<String, Object> entry : beansWithAnnotationMap.entrySet()){
            clazz = entry.getValue().getClass();
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            if (methods != null) {
                for (Method method : methods) {
                    NoLogin noLoginOperation = AnnotationUtils.findAnnotation(method, NoLogin.class);
                    if (null != noLoginOperation) {
                        System.out.println(noLoginOperation.value());
                        ignoreList.addAll(Arrays.asList(noLoginOperation.value()));
                    }
                }
            }
        }

        // 配置文件url地址
        List<String> ignoreUrls = securityProperties.getIgnoreUrls();
        String[] knife4jUrls = new String[]{"/doc.html","/webjars/**","/img.icons/**","/swagger-resources/**","/v2/api-docs"};
        ignoreUrls.addAll(Arrays.asList(knife4jUrls));
        ignoreList.addAll(ignoreUrls);

        return ignoreList;
    }

}
